SoftwareDesign2018年1月号「使えるシェルスクリプトの書き方」特集が面白かった件
AWSを日常的に触っていると、シェルスクリプトを書く機会が山のようにあります。あるんだけれど、どうにも考え方が特殊というか、他の一般的なプログラミング言語とは扱い方が違うというか、使いこなせて無い感を強く持ってました。
そんな折、SoftwareDesign2018年1月号でシェルスクリプト特集があったので、渡りに船とばかり購入して読んでみたところ、むっちゃええ感じの特集だったのでここに紹介いたします。
普段からシェルスクリプト書いているんだけれど、「これで良いんかなぁ」というモヤモヤ感をお持ちの方には有用なヒントが満載の特集だと思いますYO
__ (祭) ∧ ∧ Y ( ゚Д゚) Φ[_ソ__y_l〉 シェルスクリプトダワッショイ |_|_| し'´J
以下、コマンド例は、bashを前提としています。
第1章「使えるシェルスクリプトにするために」
最初の章では、「使えるシェルスクリプト=長く使い続けられるシェルスクリプト」として、気をつけておくべきTIPSが多数紹介されています。
個人的に、「こりゃええわぁ」と思ったところを紹介していきます。
パラメータ展開でデフォルト値を組み込もう
これ全然知らない書き方でした。
変数への値代入時に「変数が空だったらデフォルト値を代入する」という、パラメータ展開が可能です。
target=${1:-/tmp}
この例だと、第1引数が空白の時"/tmp"を変数targetに代入します。シェルスクリプトでは、引数をとることがよくあると思いますが、指定されなかった場合のデフォルト値代入に使いやすいんじゃないでしょうか。
表記 | 意味 |
---|---|
${変数:-値} | 変数が空の場合、値を返す |
${変数:=値} | 変数が空の場合、値を代入する |
bashのパラメータ展開は、他にも沢山あります。自分が見た中ではこちらが非常によくまとまっていたので、参考までに紹介します。
一時ファイル作成はmktempコマンドを使おう
恥ずかしながらこれも、全然知りませんでした。こんな簡単に一時ファイル作れるんですね。以下コマンド例を、引用します。
#!/bin/bash tmpfile=$(mktemp) date > $tmpfile ls -l > $tmpfile cat $tmpfile
ユニークな一時ファイルをさくっと作れるmktempは、いろいろ使いみちが多そうです。
その他
その他にも、以下の内容が掲載されていました。基本中の基本のものも有りますが、改めて基礎を押さえておくことで、普段の作業が改善しそうなものが多数ありました。
- ユーザーフレンドリーなシェルスクリプトにしよう
- インタプリタを明示しよう
- コマンドの終了ステータスを利用しよう
- エラーがあれば止めよう
- スクリプトの終了ステータスを決めよう
- 変数を利用しよう
- 関数で機能を分ける
- 大量のファイルをcp/mvする場合の対策
- ヒアドキュメントを使ってファイルを生成しよう
- シンプルなコマンドを使おう
- いまさらedを使う案
- -xvでコマンド実行状態を表示する
- 教材を探してみよう
第2章「シェルスクリプトはなぜこんなに書きにくいのか」
個人的に一番おもしろかった章がこれです。
第1の躓きポイント「構文がわかりにくい」
はっきり言って、シェルスクリプトは「書きにくい」。断言できますね。はい。
一貫性の無い文法に混乱し、配列の長さや値の参照もそれを使ったループ処理を書くのもやたらと面倒で、処理結果の出力も一筋縄に行かず・・・
これに関しては、以下のように文中で言及されています。
シェルスクリプトの一見不可解な文法も、「その行を普通にコマンド列として実行するとどう解釈されるか」ということを軸に読めば、実に単純なルールに則っているということがわかります。
ということです。なぜ変数の代入時に前後に空白を入れてはいけないか? 逆に、なぜ条件分岐を記述するときは前後の空白が必須なのか? というところも、ifや[などの記号がコマンドであるということを理解すれば話が早いとのことです。
今までは、書式を全部丸暗記しないと駄目と思ってたんですが、原理原則として全てがコマンドとして処理されるということであれば、それをベースに記号の羅列を眺めることで、より理解が深まることがわかりました。
第2の躓きポイント「引数と戻り値が面倒!」
シェルスクリプトの関数を、一般的なプログラミング言語の関数と同じと思うと、痛い目を見ます。
シェルスクリプトは一般的なプログラミング言語とは異なり、引数や戻り値とは別の経路で主要なデータをやり取りするように設計された言語なのです。
衝撃でした。全く知りませんでした。自分で作成した関数を、こんな感じでパイプでつなげることが可能です。
find ./ -name "*.txt" | my_function | sort -n > ouput.txt
値を渡して、値を返すのではなく、標準入出力で処理できるのであれば、逐次処理の中で関数を読まなくてもすむ場合も非常に多いんじゃないでしょうか。
第3の躓きポイント「繰り返し処理が面倒!」
これも面倒なポイントですが、基本的にシェルスクリプトにおいて、繰り返し処理に配列と使う必要は無いとのことです。
ここらへんは、xargsやwhileとreadを活用することで、シンプルに配列を使わずに、各処理を実行する例が記載されています。
まとめ
上記躓きポイントから、そもそもシェルスクリプトに向いている処理、向いていない処理を把握しておくことが大事とまとめられています。
①シェルスクリプトは原則として、シェルの一般的なコマンド列を並べて書いただけのものである
②シェルスクリプトの中のコマンドや関数は、原則として引数と戻り値ではなく標準入出力でデータをやりとりし、1行1レコードの形式のデータを連続して加工することに特化した作りになっている
例えば、ステートフルな処理が必要だったり、対話コマンドが必要なものは、そもそもシェルスクリプトでやるには無理があるから他の方法を使いなさいよー、という事ですね。
書籍では、具体的にコード例を示しながら、逐次処理でやっていた内容が最終的に非常にシンプルに実装される過程を一つ一つ確認できます。
シェルスクリプトの書き方についての原理原則を、非常にわかりやすく説明してくれている章だったので感銘を受けました。この章は、本当によくできていると思います。感激。
第3章「パフォーマンス重視のシェルスクリプトやコマンドの操作」
大量のファイルやデータを処理する時に、OS上のキャッシュの使い方や、シェルスクリプトにおける並列処理などの方法が解説された章。
単純にファイルコピーをパフォーマンス計測する場合でも、OS上のコピー対象ファイルのキャッシュ有無で全くパフォーマンスが異なる事実。キャッシュの確認、および開放方法などが紹介されています。
- キャッシュ削除前後で、キャッシュ容量が変わっている例(Amazon Linux)
$ free -m total used free shared buffers cached Mem: 993 470 522 0 130 264 -/+ buffers/cache: 76 917 Swap: 0 0 0 $ echo 3 | sudo tee /proc/sys/vm/drop_caches 3 $ free -m total used free shared buffers cached Mem: 993 74 918 0 0 24 -/+ buffers/cache: 49 944 Swap: 0 0 0
この辺に絡めて、コマンド実行中のtopコマンドによるメモリ使用量の把握方法などが解説されています。
また、大量のファイルを扱う処理を記述する時のジレンマについても、凄くリアルに書かれていて共感します。ここも面白かったので、ちょっと長いですが引用します。
シェルスクリプトもコマンドも使わないと頑なに使って、自分で何かデータ処理のプログラムを書くとしましょう。たぶん、DRAMの容量を気にするくらいのデータを扱う場合は、コマンドのようにファイルをうまく使う必要があるかもしれません。
また、データの条件抽出などはgrepやawkだけで十分だとすると、余計なコードも書かず、余計なデータも読まず前段にgrepやawkを置いて標準入力を使って読み込むことになります。(中略)
・・・・とやっていくと、自然にコマンドのような作りになります。そして、シェルスクリプトで組み合わせてしまい、誓いを破ることになります。
こういうことをやりだすと、1つの言語で統一したいのにシェルが間に入り込み、コードの管理が大変になって、あまり良いように感じることはないかもしれません。
しかし、どうしても普段から端末でコマンドをいじっていると、よほどの理由がなければそうしてしまう引力みたいなものが働きます。多くのプログラミング言語では、型がどうのこうの、ガベージコレクションがどうのこうのと、プロセス中でのメモリの使い方に大きな注意が向けられています。ですので、プロセス間の通信やファイルシステムとのつなぎみたいなものはどうしても後付になります。
つなぎの仕事は、何をどう文句を言おうがシェルが一番得意なわけです。
Linux上でファイルを意識した操作を実装しようとしたとき、シェルで組むか、その他のスクリプト言語などで組むか迷う場合も結構あるんじゃないでしょうか。自分も、AWS関連の処理を組む時に、Pythonかbashを使うか迷うときがよくあります。
そんな時、上記で引用した「プロセス間の通信やファイルシステムとのつなぎみたいなものは、何をどう文句を言おうがシェルが一番得意なわけです。」を意識して、実装指針を選択すると良さそうです。
この章の後半では、xargsを利用した処理の並列化や、それが有効な状況などについて詳しく触れられているので、普段から大きなデータをコマンドラインで処理している人には、有用な内容かと思います。
第5章「シェルスクリプトでWebスクレイピング」
「それ、シェルスクリプトでやるのか?」っという内容ですが、curlやwgetやjqや、XML/HTMLパーサのxmllint等の代表的な使い方が紹介されています。
取得したHTMLを上記コマンドや、awkや、grepや、sedを利用した文字列の整形を順を追って確認できるので、シェルスクリプトでやる代表的な文字列処理を、順を追って確認できます。
Webから情報を持ってきてあれこれ整形して表示するという、面白げなユースケースで関連知識を体験できるので面白い章でした。実際にスクレイピングするなら、PythonなりRubyなりもっと向いている言語が山のようにあると思いますが、シェルスクリプトで実装する代表的な処理が、身近な題材を元に挙げられているので、非常に参考になります。
シェルスクリプトのあるあるがコンパクトに纏まった特集
いやぁ、面白い特集でした。
自分としては、特に「第2章 シェルスクリプトはなぜこんなに書きにくいのか!?」がドツボにはまりました。今までモヤッとしてたことが、非常に明快に説明されていたので、これから先、シェルスクリプトで組むべきかそうじゃないのか?という判断を、もっと正確にできるようになる気がします。既に自分が書いたものでも、見直したいものが多数あるというね・・・
普段シェルスクリプトを触っている人で、いまいちその使いこなしに納得が行かない人には、参考になる内容がいろいろ網羅されていると思うので、一度手にとってみてはいかがでしょうか。
それでは、今日はこのへんで。濱田(@hamako9999)でした。